﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects.DataClasses;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;

namespace DG.Common.EF.Reflection
{
    public class EntityConceptualInfosWrapper
    {

        #region Properties

        public ContextConceptualInfos ContextConceptualInfos { get; private set; }
        public Type Type { get; private set; }
        public EntityEdmInfos EntityEdmConceptualInfos { get; private set; }
        public String PhysicalTableName { get; private set; }
        public ReadOnlyCollection<PropertyInfo> Properties { get; private set; }
        public ReadOnlyCollection<PropertyInfo> PrimitiveProperties { get; private set; }
        public ReadOnlyCollection<PropertyInfo> NavigationProperties { get; private set; }
        public ReadOnlyCollection<PropertyInfo> NavigationCollectionProperties { get; private set; }
        public ReadOnlyCollection<PropertyInfo> Keys { get; private set; }
        public ReadOnlyCollection<PropertyInfo> AutoIncrements { get; private set; }
        public Dictionary<NavigationProperty, PropertyInfo[]> ForeignKeys { get; private set; }
        public Dictionary<PropertyInfo, EntityPropertyConceptualInfos> PropertiesMeta { get; private set; }
        
        #endregion

        #region Methods

        private void DiscoverEDMX()
        {
            EntityEdmConceptualInfos = ContextConceptualInfos.EntitiesEdmInfos[Type];
        }
        private void DiscoverProperties()
        {
            Properties = new ReadOnlyCollection<PropertyInfo>(EntityEdmConceptualInfos.DataMembers.Select(p => Type.GetProperty(p.Name)).ToList());
        }
        private void DiscoverPrimitiveProperties()
        {
            PrimitiveProperties = new ReadOnlyCollection<PropertyInfo>(EntityEdmConceptualInfos.PrimitiveProperties.Select(p => Type.GetProperty(p.Name)).ToList());
        }
        private void DiscoverNavigationCollectionProperties()
        {
            NavigationCollectionProperties = new ReadOnlyCollection<PropertyInfo>(EntityEdmConceptualInfos.DataMembers.Where(e => e.TypeUsage.EdmType.GetType() == typeof(CollectionType))
                .Select(p => Type.GetProperty(p.Name)).ToList());
        }
        private void DiscoverNavigationProperties()
        {
            NavigationProperties = new ReadOnlyCollection<PropertyInfo>(Properties.Except(PrimitiveProperties).Except(NavigationCollectionProperties).ToList());
        }
        private void DiscoverKeys()
        {
            Keys = new ReadOnlyCollection<PropertyInfo>(EntityEdmConceptualInfos.Keys.Select(p => Type.GetProperty(p.Name)).ToList());
        }
        private void DiscoverForeignKeys()
        {
            ForeignKeys = EntityEdmConceptualInfos.FKeys.ToDictionary(k=> k.Key, v => v.Value.Select(p=> Type.GetProperty(p.Name)).ToArray());
        }
        private void DiscoverAutoIncrements()
        {
            AutoIncrements = new ReadOnlyCollection<PropertyInfo>(EntityEdmConceptualInfos.AutoIncrementProperties.Select(p => Type.GetProperty(p.Name)).ToList());
        }
        private void DiscoverPropertiesMetaInfos()
        {
            PropertiesMeta = Properties.ToDictionary(k=> k, p => new EntityPropertyConceptualInfos(this, p));
        }

        public object[] GetKeysValues(object item)
        {
            {
                return Keys.Select(e => e.GetValue(item, null)).ToArray();
            }
        }

        public PropertyInfo FindKey<T>() where T : EntityObject
        {
            var et = typeof(T);
            var at = typeof(EdmScalarPropertyAttribute);
            var props = from p in et.GetProperties()
                        let attr = Attribute.GetCustomAttribute(p, at) as EdmScalarPropertyAttribute
                        select new { Property = p, IsKey = attr != null && attr.EntityKeyProperty };
            return props.Where(p => p.IsKey).Select(p => p.Property).FirstOrDefault();
        }

        #endregion

        #region Constructors


        public EntityConceptualInfosWrapper(ContextConceptualInfos contextConceptualInfos, Type dbSetType)
        {
            this.ContextConceptualInfos = contextConceptualInfos;
            this.Type = dbSetType;


            #region Discoverings
            DiscoverEDMX();
            DiscoverProperties();
            DiscoverPrimitiveProperties();
            DiscoverNavigationCollectionProperties();
            DiscoverNavigationProperties();
            DiscoverKeys();
            DiscoverForeignKeys();
            DiscoverAutoIncrements();
            DiscoverTableName();
            DiscoverPropertiesMetaInfos();

            #endregion
        }

        private void DiscoverTableName()
        {
            var mw = ContextConceptualInfos.MetadataWorkspace;
            var container = mw.GetItems<EntityContainer>(DataSpace.SSpace).Single().EntitySets;
            var entitySet = container.SingleOrDefault(e => Type.Name == e.Name);
            PhysicalTableName = entitySet== null ? null : entitySet.Table;
        }

        #endregion

        #region Subsclasses

        public class InstanceInfos
        {
            public IEnumerable<InstancePropertyInfos> Values { get; private set; }

            public object[] Keys { get; private set; }

            public InstanceInfos(EntityConceptualInfosWrapper listEditor, object entity)
            {
                this.Keys = listEditor.GetKeysValues(entity);
                //Values = wrapper.ItemPropertiesInfos.Select(p => new InstancePropertyInfos(p, entity));
            }
        }
        public class InstancePropertyInfos
        {
            public EntityPropertyConceptualInfos ConceptualInfos { get; private set; }
            public Object Item { get; private set; }

            public object[] Keys { get; private set; }
            public object Value { get; private set; }

            public InstancePropertyInfos(EntityPropertyConceptualInfos conceptualInfos, object entity)
            {
                this.ConceptualInfos = conceptualInfos;
                this.Item = entity;
                this.Keys = conceptualInfos.Wrapper.GetKeysValues(entity);

                this.Value = conceptualInfos.Property.GetValue(entity, null);

            }
        }

        #endregion
    }

    public class EntityPropertyConceptualInfos
    {
        public EntityConceptualInfosWrapper Wrapper { get; private set; }
        public PropertyInfo Property { get; private set; }
        public bool IsKey { get; private set; }
        public bool IsEnum { get; private set; }
        public bool IsNavigation { get; private set; }
        public bool IsNavigationCollection { get; private set; }
        public bool IsPrimitive { get; private set; }
        public bool IsForeignKey { get; private set; }
        public bool IsAutoIncrement { get; private set; }

        public Type Type { get { return Property.PropertyType; } }
        public string Name { get { return Property.Name; } }

        public string DisplayName
        {
            get
            {
                var displayAtribute = (Property.GetCustomAttributes(true)).OfType<DisplayAttribute>().FirstOrDefault();

                return displayAtribute == null ? Name : displayAtribute.Name;
            }
        }
        public string Description
        {
            get
            {
                var displayAtribute = (Property.GetCustomAttributes(true)).OfType<DisplayAttribute>().FirstOrDefault();

                return displayAtribute == null ? null : displayAtribute.Description;
            }
        }
        public string Prompt
        {
            get
            {
                var displayAtribute = (Property.GetCustomAttributes(true)).OfType<DisplayAttribute>().FirstOrDefault();

                return displayAtribute == null ? null : displayAtribute.Prompt;
            }
        }
        

        public EntityPropertyConceptualInfos(EntityConceptualInfosWrapper wrapper, PropertyInfo property)
        {
            Wrapper = wrapper;
            Property = property;
            IsKey = wrapper.Keys.Contains(Property);
            IsEnum = Property.PropertyType.IsEnum;
            IsNavigation = wrapper.NavigationProperties.Contains(property);
            IsNavigationCollection = wrapper.NavigationCollectionProperties.Contains(property);
            IsPrimitive = wrapper.PrimitiveProperties.Contains(property);
            IsForeignKey = wrapper.ForeignKeys.Values.Any(fk => fk.Contains(property));
            IsAutoIncrement = wrapper.AutoIncrements.Contains(property);
        }
    }
}
